package com.tmt.im.wallet.service;

import com.alibaba.fastjson2.JSONObject;
import com.tmt.im.common.enums.RedPackageType;
import com.tmt.im.common.enums.WalletEventType;
import com.tmt.im.common.pojo.*;
import com.tmt.im.common.yunxin.IYunXinMsgSender;
import com.tmt.im.jpa.entity.ChatSessionMember;
import com.tmt.im.jpa.entity.IMUser;
import com.tmt.im.jpa.entity.RedPackage;
import com.tmt.im.jpa.entity.RedPackageDetail;
import com.tmt.im.jpa.repository.*;
import com.tmt.im.wallet.config.beans.CurrentServiceProperties;
import com.tmt.im.wallet.config.beans.RedPackageRuleProperties;
import com.tmt.im.wallet.config.beans.SensitiveKeysProperties;
import com.tmt.im.wallet.controler.vo.*;
import com.tmt.im.wallet.helper.IgniteQueryHelper;
import com.tmt.im.wallet.mapstruct.RedPackageDetailMapStruct;
import com.tmt.springboot.common.helper.IdSnowFlake;
import com.tmt.springboot.common.restful.ExecuteResult;
import com.tmt.springboot.common.restful.IExecuteResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteSemaphore;
import org.apache.ignite.transactions.Transaction;
import org.apache.ignite.transactions.TransactionConcurrency;
import org.apache.ignite.transactions.TransactionIsolation;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.security.Principal;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description TODO
 * @Author wuyi
 * @Date 2024/9/2 14:41
 * @Version 1.0
 **/
@Slf4j
@Service
public class RedPackageService extends BaseService {

    private final IMUserRepo imUserRepo;

    private final RedPackageRepo redPackageRepo;

    private final RedPackageSkinRepo redPackageSkinRepo;

    private final RedPackageDetailRepo redPackageDetailRepo;

    private final ChatSessionMemberRepo sessionMemberRepo;

    private final PasswordEncoder passwordEncoder;

    private final SensitiveKeysProperties keysProperties;

    private final RedPackageRuleProperties ruleProperties;

    private final IYunXinMsgSender iYunXinMsgSender;

    private final RedPackageDetailMapStruct redPackageDetailMapStruct;

    private final CurrentServiceProperties serviceProperties;

    public RedPackageService(Ignite ignite,
                             IMUserRepo imUserRepo,
                             RedPackageRepo redPackageRepo,
                             RedPackageSkinRepo redPackageSkinRepo,
                             RedPackageDetailRepo redPackageDetailRepo,
                             ChatSessionMemberRepo sessionMemberRepo,
                             PasswordEncoder passwordEncoder,
                             SensitiveKeysProperties keysProperties,
                             RedPackageRuleProperties ruleProperties,
                             IYunXinMsgSender iYunXinMsgSender,
                             RedPackageDetailMapStruct redPackageDetailMapStruct,
                             CurrentServiceProperties serviceProperties) {
        super(ignite);
        this.imUserRepo = imUserRepo;
        this.redPackageRepo = redPackageRepo;
        this.redPackageDetailRepo = redPackageDetailRepo;
        this.redPackageSkinRepo = redPackageSkinRepo;
        this.sessionMemberRepo = sessionMemberRepo;
        this.passwordEncoder = passwordEncoder;
        this.keysProperties = keysProperties;
        this.ruleProperties = ruleProperties;
        this.iYunXinMsgSender = iYunXinMsgSender;
        this.redPackageDetailMapStruct = redPackageDetailMapStruct;
        this.serviceProperties = serviceProperties;
    }

    public IExecuteResult<Long> createRedPackage(Long currentUserId, NewRedPackageRequest request, Principal principal) {
        IMUserPoJo userPoJo = userCache.get(currentUserId);
        WalletPoJo walletPoJo = walletCache.get(userPoJo.getWalletId());
        if (!StringUtils.hasText(walletPoJo.getPassword())) {
            return ExecuteResult.failure("请先设置支付密码", null);
        }

        if (!passwordEncoder.matches(request.getPayPassword(), walletPoJo.getPassword())) {
            return ExecuteResult.failure("支付密码错误", null);
        }

        StringBuilder illegalContent = new StringBuilder();
        if (keysProperties.exist(request.getTitle(), illegalContent)) {
            return ExecuteResult.failure(MessageFormat.format("标题文字内容涉嫌违规:{0}", illegalContent.toString()), null);
        }

        if (request.getType() == RedPackageType.PERSONAL) {
            if (CollectionUtils.isEmpty(request.getReceiverIds())) {
                return ExecuteResult.failure("必须指定可领取红包的好友", null);
            }
            if (request.getReceiverIds().contains(currentUserId)) {
                return ExecuteResult.failure("不能给自己发红包", null);
            }
            //检查：必须是好友且不在对方的黑名单中。
            IExecuteResult<Boolean> valid = validateReceiverIds(currentUserId, request.getReceiverIds().get(0));
            if (valid.getCode() != ExecuteResult.SUCCESS_CODE) {
                return ExecuteResult.failure(valid.getMsg(), null);
            }
        } else {
            if (request.getSessionId() == null) {
                return ExecuteResult.failure("聊天群组ID不能为空", null);
            }
            ChatSessionPoJo sessionPoJo = sessionCache.get(request.getSessionId());
            if (sessionPoJo == null || sessionPoJo.isDeleted()) {
                return ExecuteResult.failure(MessageFormat.format("指定的聊天群组(ID={0})不存在",
                        "" + request.getSessionId()), null);
            }

            if (request.getType() == RedPackageType.EXCLUSIVE) {
                if (CollectionUtils.isEmpty(request.getReceiverIds())) {
                    return ExecuteResult.failure("必须指定可领取红包的群成员", null);
                }
                if (request.getReceiverIds().contains(currentUserId)) {
                    return ExecuteResult.failure("不能给自己发红包", null);
                }
            }
        }

        Long minAmount = null;
        Long maxAmount = null;
        IExecuteResult<Long> result = null;
        switch (request.getType()) {
            case PERSONAL -> {
                minAmount = ruleProperties.getPersonal() != null && ruleProperties.getPersonal().getMinAmount() != null
                        ? ruleProperties.getPersonal().getMinAmount() : ruleProperties.getMinAmount();
                maxAmount = ruleProperties.getPersonal() != null && ruleProperties.getPersonal().getMaxAmount() != null
                        ? ruleProperties.getPersonal().getMaxAmount() : ruleProperties.getMaxAmount();
            }
            case EXCLUSIVE -> {
                minAmount = ruleProperties.getExclusive() != null && ruleProperties.getExclusive().getMinAmount() != null
                        ? ruleProperties.getExclusive().getMinAmount() : ruleProperties.getMinAmount();
                maxAmount = ruleProperties.getExclusive() != null && ruleProperties.getExclusive().getMaxAmount() != null
                        ? ruleProperties.getExclusive().getMaxAmount() : ruleProperties.getMaxAmount();
            }
            default -> {
                minAmount = ruleProperties.getLucky() != null && ruleProperties.getLucky().getMinAmount() != null
                        ? ruleProperties.getLucky().getMinAmount() : ruleProperties.getMinAmount();
                maxAmount = ruleProperties.getLucky() != null && ruleProperties.getLucky().getMaxAmount() != null
                        ? ruleProperties.getLucky().getMaxAmount() : ruleProperties.getMaxAmount();
            }
        }

        if (request.getAmount().compareTo(maxAmount) > 0) {
            return ExecuteResult.failure(MessageFormat.format("{0}金额不能超过{1}元", request.getType().getName(), "" + (maxAmount / 100)), null);
        } else if (request.getAmount().compareTo(minAmount) < 0) {
            return ExecuteResult.failure(MessageFormat.format("{0}金额不能少于{1}元", request.getType().getName(), "" + (minAmount / 100)), null);
        }

        return _createRedPackage(currentUserId, request, principal);
    }

    public IExecuteResult<RedPackageDetailInfo> receiveRedPackage(Long currentUserId, Long redPackageId, Principal principal) {
        RedPackagePoJo redPackagePoJo = redPackageCache.get(redPackageId);
        if (redPackagePoJo == null) {
            return ExecuteResult.failure("目标红包不存在", null);
        }
        //获取红包的类型
        RedPackageType type = Arrays.stream(RedPackageType.values())
                .filter(x -> x.name().equals(redPackagePoJo.getType())).findFirst()
                .orElse(null);
        if (type == null) {
            return ExecuteResult.failure("目标红包的类型异常", null);
        }

        if (type == RedPackageType.PERSONAL) {
            Long receiverId = Long.parseLong(redPackagePoJo.getReceiverIds());
            if (!Objects.equals(currentUserId, receiverId) && !Objects.equals(currentUserId, redPackagePoJo.getCreatorId())) {
                return ExecuteResult.failure("无权领取他人的个人红包", null);
            }
        } else {
            if (redPackagePoJo.getSessionId() == null) {
                return ExecuteResult.failure("聊天群组ID不能为空", null);
            }
            ChatSessionPoJo sessionPoJo = sessionCache.get(redPackagePoJo.getSessionId());
            if (sessionPoJo == null || sessionPoJo.isDeleted()) {
                return ExecuteResult.failure("目标群组不存在", null);
            }

            if (type == RedPackageType.LUCKY) {
                if (!sessionPoJo.isAllowRedPackage()) {
                    return ExecuteResult.failure("目标群组已禁止抢手气红包", null);
                }
                ChatSessionMemberPoJo memberPoJo = getSessionMemberBy(currentUserId, sessionPoJo.getId());
                if (memberPoJo != null && memberPoJo.isForbidRedPackage()
                        && !Objects.equals(currentUserId, redPackagePoJo.getCreatorId())) {
                    return ExecuteResult.failure("您已被禁止在目标群组中抢手气红包", null);
                }
            } else {
                Long receiverId = Long.parseLong(redPackagePoJo.getReceiverIds());
                if (!Objects.equals(currentUserId, receiverId) && !Objects.equals(currentUserId, redPackagePoJo.getCreatorId())) {
                    return ExecuteResult.failure("无权领取他人的专属红包", null);
                }
            }
        }

        if (redPackagePoJo.isReceivedAlready() ||
                (type != RedPackageType.LUCKY && Objects.equals(currentUserId, redPackagePoJo.getCreatorId()))) {
            //如果红包已经领完，或者不是手气红包且当前用户是红包的创建者，则返回红包领取详情信息。
            return receiveRedPackageDetail(redPackageId);
        }

        return _receiveRedPackage(currentUserId, type, redPackageId);
    }

    public IExecuteResult<RedPackageStatus> queryRedPackageStatus(Long currentUserId, Long redPackageId, Principal principal) {
        RedPackagePoJo packagePoJo = redPackageCache.get(redPackageId);
        if (packagePoJo == null) {
            return ExecuteResult.failure("目标红包不存在", null);
        }

//        if (RedPackageType.PERSONAL.name().equals(packagePoJo.getType())
//                || RedPackageType.EXCLUSIVE.name().equals(packagePoJo.getType())) {
//            if (packagePoJo.getReceiverIds().contains("" + currentUserId)) {
//                return ExecuteResult.failure("只有红包指定的领取人才能查看红包状态", null);
//            }
//        } else {
//            //如果是手气红包，则群成员都可以看。
//            ChatSessionMemberPoJo memberPoJo = getSessionMemberBy(currentUserId, packagePoJo.getSessionId());
//            if (memberPoJo == null) {
//                return ExecuteResult.failure("当前用户不是群成员，无权查看红包状态", null);
//            }
//        }

        String sql = MessageFormat.format("select * from {0} where redPackageId = ?",
                RedPackageDetailPoJo.CACHE_NAME);
        Object[] params = new Object[]{redPackageId};

        //查询红包领取详情
        List<RedPackageDetailPoJo> detailPoJos = IgniteQueryHelper.findAllBy(sql, params, RedPackageDetailPoJo.class, redPackageDetailCache);

        Timestamp curTime = new Timestamp(System.currentTimeMillis());

        int status = 1;
        if (Objects.equals(packagePoJo.getType(), RedPackageType.LUCKY.name())) {
            if (detailPoJos.get(0).getExpireTime().before(curTime)) {
                //已过期。
                status = 3;
                if (packagePoJo.isReceivedAlready()
                        && detailPoJos.stream().anyMatch(x -> Objects.equals(x.getMemberId(), currentUserId))) {
                    status = 4;
                }
            } else {
                //未过期
                if (detailPoJos.stream().anyMatch(x -> Objects.equals(x.getMemberId(), currentUserId))) {
                    status = packagePoJo.isReceivedAlready() ? 4 : 5;
                }
            }
        } else {
            if (packagePoJo.isReceivedAlready()) {
                status = 2;
            } else if (detailPoJos.get(0).getExpireTime().before(curTime)) {
                //已过期。
                status = 3;
            }
        }

        RedPackageStatus packageStatus = RedPackageStatus.builder()
                .type(status)
                .build();
        return ExecuteResult.success(packageStatus);
    }

    public IExecuteResult<RedPackageDetailInfo> receiveRedPackageDetail(Long redPackageId) {
        RedPackagePoJo packagePoJo = redPackageCache.get(redPackageId);
        if (packagePoJo == null) {
            return ExecuteResult.failure("目标红包不存在", null);
        }

        String sql;
        Object[] params;
        if (List.of(RedPackageType.PERSONAL.name(), RedPackageType.EXCLUSIVE.name()).contains(packagePoJo.getType())) {
            sql = MessageFormat.format("select * from {0} where redPackageId = ?",
                    RedPackageDetailPoJo.CACHE_NAME);
            params = new Object[]{redPackageId};
        } else {
            sql = MessageFormat.format("select * from {0} where redPackageId = ? and receivedAlready = ?",
                    RedPackageDetailPoJo.CACHE_NAME);
            params = new Object[]{redPackageId, true};
        }
        //查询红包领取详情
        List<RedPackageDetailPoJo> detailPoJos = IgniteQueryHelper.findAllBy(sql, params, RedPackageDetailPoJo.class, redPackageDetailCache);

        //读取用户的群昵称。
        Map<Long, String> memberNickMap = new LinkedHashMap<>();
        if (packagePoJo.getSessionId() != null && packagePoJo.getSessionId() > 0) {
            params = new Object[1 + detailPoJos.size()];
            params[0] = packagePoJo.getSessionId();

            StringBuilder memberIds = new StringBuilder();
            for (int i = 0; i < detailPoJos.size(); i++) {
                memberIds.append(" or memberId = ?");
                params[i + 1] = detailPoJos.get(i).getMemberId();
            }
            String strMemberIds = memberIds.length() > 4
                    ? MessageFormat.format("and ({0})", memberIds.substring(4)) : "";
            //获取领取人的群昵称。
            sql = MessageFormat.format("select * from {0} where sessionId = ? {1}",
                    ChatSessionMemberPoJo.CACHE_NAME, strMemberIds);
            List<ChatSessionMemberPoJo> sessionMemberPoJos = IgniteQueryHelper.findAllBy(sql, params,
                    ChatSessionMemberPoJo.class, sessionMemberCache);
            for (ChatSessionMemberPoJo sessionMemberPoJo : sessionMemberPoJos) {
                if (StringUtils.hasText(sessionMemberPoJo.getMemberNick())) {
                    memberNickMap.put(sessionMemberPoJo.getMemberId(), sessionMemberPoJo.getMemberNick());
                }
            }
        }

        List<ReceivedDetailItem> voList = redPackageDetailMapStruct.toReceivedDetailItems(detailPoJos);
        for (ReceivedDetailItem vo : voList) {
            IMUserPoJo userPoJo = userCache.get(vo.getMemberId());
            String nick = memberNickMap.containsKey(vo.getMemberId()) ?
                    memberNickMap.get(vo.getMemberId()) : userPoJo.getNickname();
            vo.setMemberNick(nick);
            vo.setGrade(userPoJo.getGrade() != null ? userPoJo.getGrade() : 0);
            String url = "http://" + serviceProperties.getExternalClientService() + userPoJo.getAvatar();
            vo.setAvatar(url);
            vo.setFirstGood(false);
        }

        if (packagePoJo.isReceivedAlready()) {
            List<ReceivedDetailItem> tmpList = voList.stream()
                    .sorted(Comparator.comparingLong(ReceivedDetailItem::getAmount).reversed()).toList();
            tmpList.get(0).setFirstGood(true);
        }

        Long receivedAmount = detailPoJos.stream().filter(RedPackageDetailPoJo::isReceivedAlready)
                .map(RedPackageDetailPoJo::getAmount).reduce(Long::sum).orElse(0L);

        RedPackageType redPackageType = Arrays.stream(RedPackageType.values())
                .filter(x -> x.name().equals(packagePoJo.getType())).findFirst().orElse(null);
        assert redPackageType != null;

        WalletEventType eventType = null;
        switch (redPackageType) {
            case PERSONAL -> eventType = WalletEventType.SEND_PERSONAL_RED_PACKET;
            case EXCLUSIVE -> eventType = WalletEventType.SEND_EXCLUSIVE_RED_PACKET;
            default -> eventType = WalletEventType.SEND_LUCKY_RED_PACKET;
        }

        IMUserPoJo creator = userCache.get(packagePoJo.getCreatorId());
        assert creator != null;
        String avatar = "http://" + serviceProperties.getExternalClientService() + creator.getAvatar();

        String nickname = creator.getNickname();
        if (packagePoJo.getSessionId() != null) {
            sql = MessageFormat.format("select * from {0} where sessionId = ? and memberId = ?",
                    ChatSessionMemberPoJo.CACHE_NAME);
            ChatSessionMemberPoJo sessionMemberPoJo = IgniteQueryHelper.findOneBy(sql,
                    new Object[]{packagePoJo.getSessionId(), packagePoJo.getCreatorId()}, ChatSessionMemberPoJo.class, sessionMemberCache);
            if (sessionMemberPoJo != null) {
                nickname = sessionMemberPoJo.getMemberNick();
            }
        }

        RedPackageDetailInfo detailInfo = new RedPackageDetailInfo();
        detailInfo.setReceivedDetails(voList);

        detailInfo.setId(packagePoJo.getId());
        detailInfo.setTitle(packagePoJo.getTitle());
        detailInfo.setSplitCount(packagePoJo.getSplitCount());
        detailInfo.setSessionId(packagePoJo.getSessionId());
        detailInfo.setReceivedAmount(receivedAmount);
        detailInfo.setEventType(eventType.getType());
        detailInfo.setSendAmount(packagePoJo.getAmount());
        detailInfo.setSenderId(packagePoJo.getCreatorId());
        detailInfo.setSenderAvatar(avatar);
        detailInfo.setSenderLevel(creator.getGrade() != null ? creator.getGrade() : 0);
        detailInfo.setSenderName(nickname);
        detailInfo.setSendTime(packagePoJo.getCreateTime().getTime());

        if (packagePoJo.isReceivedAlready()) {
            detailInfo.setActionType(2);
        } else {
            detailInfo.setActionType(1);

            Timestamp expireTime = null;
            if (detailPoJos.size() == 0) {
                sql = MessageFormat.format("select * from {0} where redPackageId = ?", RedPackageDetailPoJo.CACHE_NAME);
                RedPackageDetailPoJo detailPoJo = IgniteQueryHelper.findOneBy(sql, new Object[]{redPackageId},
                        RedPackageDetailPoJo.class, redPackageDetailCache);
                assert detailPoJo != null;
                expireTime = detailPoJo.getExpireTime();
            } else {
                expireTime = detailPoJos.get(0).getExpireTime();
            }
            if (expireTime.before(new Timestamp(System.currentTimeMillis()))) {
                detailInfo.setActionType(3);
            }
        }
        return ExecuteResult.success(detailInfo);
    }


    @Transactional(readOnly = true)
    public IExecuteResult<ReceivedRedPackageResponse> getReceivedRedPackagePage(Long currentUserId,
                                                                                RedPackagePageQueryRequest request) {
        Timestamp sTime = null;
        Timestamp eTime = null;
        DateFormat sdf1 = new SimpleDateFormat("yyyy-MM");
        DateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (request.getStartTime() != null) {
            try {
                Date sDate = sdf1.parse(request.getStartTime());
                String str = sdf2.format(sDate);
                sTime = Timestamp.valueOf(str);

                // 使用Calendar类来获取月份
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(sDate);
                calendar.add(Calendar.MONTH, 1);
                str = sdf2.format(calendar.getTime());
                //设置默认的截止时间。
                eTime = Timestamp.valueOf(str);
            } catch (Exception ex) {
                return ExecuteResult.failure("开始时间(startTime)参数格式异常，仅支持yyyy-MM格式", null);
            }
        }

        if (request.getEndTime() != null) {
            try {
                Date sDate = sdf1.parse(request.getEndTime());
                String str = sdf2.format(sDate);
                eTime = Timestamp.valueOf(str);
            } catch (Exception ex) {
                return ExecuteResult.failure("截止时间(endTime)参数格式异常，仅支持yyyy-MM格式", null);
            }
        }

        PageRequest pageRequest = PageRequest.of(request.getPageIndex(), request.getPageSize());

        Page<RedPackageDetail> redPackageDetails = null;
        if (request.getDesc()) {
            redPackageDetails = redPackageDetailRepo.queryOrderByCreateTimeDescBy(currentUserId, sTime, eTime, pageRequest);
        } else {
            redPackageDetails = redPackageDetailRepo.queryOrderByCreateTimeAscBy(currentUserId, sTime, eTime, pageRequest);
        }

        List<ReceivedRedPackageItem> list = convertToReceivedRedPackageItemList(redPackageDetails.getContent());
        ReceivedRedPackageResponse response = new ReceivedRedPackageResponse((int) redPackageDetails.getTotalElements(), list);
        BeanUtils.copyProperties(request, response);
        return ExecuteResult.success(response);
    }

    @Transactional(readOnly = true)
    public IExecuteResult<SendRedPackageResponse> getSendRedPackagePage(Long currentUserId, RedPackagePageQueryRequest request) {
        Timestamp sTime = null;
        Timestamp eTime = null;
        DateFormat sdf1 = new SimpleDateFormat("yyyy-MM");
        DateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (request.getStartTime() != null) {
            try {
                Date sDate = sdf1.parse(request.getStartTime());
                String str = sdf2.format(sDate);
                sTime = Timestamp.valueOf(str);

                // 使用Calendar类来获取月份
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(sDate);
                calendar.add(Calendar.MONTH, 1);
                str = sdf2.format(calendar.getTime());
                //设置默认的截止时间。
                eTime = Timestamp.valueOf(str);
            } catch (Exception ex) {
                return ExecuteResult.failure("开始时间(startTime)参数格式异常，仅支持yyyy-MM格式", null);
            }
        }

        if (request.getEndTime() != null) {
            try {
                Date sDate = sdf1.parse(request.getEndTime());
                String str = sdf2.format(sDate);
                eTime = Timestamp.valueOf(str);
            } catch (Exception ex) {
                return ExecuteResult.failure("截止时间(endTime)参数格式异常，仅支持yyyy-MM格式", null);
            }
        }

        PageRequest pageRequest = PageRequest.of(request.getPageIndex(), request.getPageSize());
        Page<RedPackage> redPackages = null;
        if (request.getDesc()) {
            redPackages = redPackageRepo.queryOrderByCreateTimeDescBy(currentUserId, sTime, eTime, pageRequest);
        } else {
            redPackages = redPackageRepo.queryOrderByCreateTimeAscBy(currentUserId, sTime, eTime, pageRequest);
        }

        List<SendRedPackageItem> list = convertToSendRedPackageItemList(redPackages.getContent());
        SendRedPackageResponse response = new SendRedPackageResponse((int) redPackages.getTotalElements(), list);
        BeanUtils.copyProperties(request, response);
        return ExecuteResult.success(response);
    }

    public ChatSessionMemberPoJo getSessionMemberBy(Long memberId, Long tid) {
        String sql = MessageFormat.format("select * from {0} where memberId =? and sessionId =?", ChatSessionMemberPoJo.CACHE_NAME);
        return IgniteQueryHelper.findOneBy(sql, new Object[]{memberId, tid}, ChatSessionMemberPoJo.class, sessionMemberCache);
    }

    /*************************************私有方法******************************************/

    private IExecuteResult<Long> _createRedPackage(Long currentUserId, NewRedPackageRequest request,
                                                   Principal principal) {
        IMUserPoJo userPoJo = userCache.get(currentUserId);
        try (IgniteSemaphore semaphore = ignite.semaphore("wallet:" + currentUserId, 1, true, true)) {
            if (semaphore.tryAcquire(3, TimeUnit.SECONDS)) {
                try {

                    WalletPoJo walletPoJo = walletCache.get(userPoJo.getWalletId());
                    if (walletPoJo.getBalance().compareTo(request.getAmount()) < 0) {
                        return ExecuteResult.failure("钱包余额不足", null);
                    }

                    Long redPackageId = IdSnowFlake.getId();
                    RedPackagePoJo packagePoJo = null;

                    //创建乐观串行事务。
                    try (Transaction tx = ignite.transactions().txStart(TransactionConcurrency.OPTIMISTIC,
                            TransactionIsolation.SERIALIZABLE)) {
                        walletPoJo = walletCache.get(userPoJo.getWalletId());
                        if (walletPoJo.getBalance().compareTo(request.getAmount()) < 0) {
                            return ExecuteResult.failure("钱包余额不足", null);
                        }
                        //从钱包中扣除红包金额.
                        long balance = walletPoJo.getBalance() - request.getAmount();
                        walletPoJo.setBalance(balance);

                        //创建余额变动记录.
                        Timestamp expireTime = null;
                        WalletEventType eventType = null;
                        switch (request.getType()) {
                            case PERSONAL -> {
                                eventType = WalletEventType.SEND_PERSONAL_RED_PACKET;
                                int minutes = ruleProperties.getPersonal() != null && ruleProperties.getPersonal().getExpireMinutes() != null
                                        ? ruleProperties.getPersonal().getExpireMinutes() : ruleProperties.getExpireMinutes();
                                expireTime = Timestamp.from(OffsetDateTime.now().toInstant().plus(minutes, ChronoUnit.MINUTES));
                            }
                            case EXCLUSIVE -> {
                                eventType = WalletEventType.SEND_EXCLUSIVE_RED_PACKET;
                                int minutes = ruleProperties.getExclusive() != null && ruleProperties.getExclusive().getExpireMinutes() != null
                                        ? ruleProperties.getExclusive().getExpireMinutes() : ruleProperties.getExpireMinutes();
                                expireTime = Timestamp.from(OffsetDateTime.now().toInstant().plus(minutes, ChronoUnit.MINUTES));
                            }
                            default -> {
                                eventType = WalletEventType.SEND_LUCKY_RED_PACKET;
                                int minutes = ruleProperties.getLucky() != null && ruleProperties.getLucky().getExpireMinutes() != null
                                        ? ruleProperties.getLucky().getExpireMinutes() : ruleProperties.getExpireMinutes();
                                expireTime = Timestamp.from(OffsetDateTime.now().toInstant().plus(minutes, ChronoUnit.MINUTES));
                            }
                        }
                        WalletChangeDetailPoJo walletDetailPoJo = WalletChangeDetailPoJo.builder()
                                .id(IdSnowFlake.getId())
                                .eventId(redPackageId)
                                .eventType(eventType.name())
                                //扣减为负数。
                                .amount(-request.getAmount())
                                .walletId(walletPoJo.getId())
                                .balance(balance)
                                .createTime(new Timestamp(System.currentTimeMillis()))
                                .creatorId(currentUserId)
                                .version(0L)
                                .insert(true)
                                .build();

                        //创建红包记录.
                        String receiverIds = null;
                        if (!CollectionUtils.isEmpty(request.getReceiverIds())) {
                            receiverIds = request.getReceiverIds().stream()
                                    .map(x -> "" + x).reduce((i, o) -> i + "," + o).orElse(null);
                        }
                        packagePoJo = RedPackagePoJo.builder()
                                .id(redPackageId)
                                .title(request.getTitle())
                                .skinId(request.getSkinId())
                                .type(request.getType().name())
                                .amount(request.getAmount())
                                .splitCount(request.getSplitCount())
                                .receivedAlready(false)
                                .receiverIds(receiverIds)
                                .sessionId(request.getSessionId())
                                .creatorId(currentUserId)
                                .createTime(new Timestamp(System.currentTimeMillis()))
                                .version(0L)
                                .insert(true)
                                .build();

                        Map<RedPackageDetailKey, RedPackageDetailPoJo> map = new LinkedHashMap<>();
                        int splitCount = request.getType() == RedPackageType.LUCKY ? request.getSplitCount()
                                : request.getReceiverIds().size();
                        Long balanceAmount = request.getAmount();
                        Long splitAmount = null;
                        for (int i = 0; i < splitCount; i++) {
                            //计算一个切分额度.
                            splitAmount = (i < splitCount - 1) ? Math.round(balanceAmount * Math.random()) : balanceAmount;
                            balanceAmount = balanceAmount - splitAmount;
                            //创建红包领取详情记录.
                            Long memberId = request.getType() != RedPackageType.LUCKY ? request.getReceiverIds().get(i) : null;
                            Long id = IdSnowFlake.getId();
                            RedPackageDetailPoJo detailPoJo = RedPackageDetailPoJo.builder()
                                    .id(id)
                                    .mid(id)
                                    .redPackageId(packagePoJo.getId())
                                    .redPackageType(request.getType().name())
                                    .sessionId(request.getSessionId())
                                    .amount(splitAmount)
                                    .memberId(memberId)     //提取预设好领取人.
                                    .receivedAlready(false) //标记为尚未领取.
                                    .expireTime(expireTime)
                                    .refunded(false)
                                    .creatorId(currentUserId)
                                    .createTime(new Timestamp(System.currentTimeMillis()))
                                    .version(0L)
                                    .insert(true)
                                    .build();
                            map.put(detailPoJo.getKey(), detailPoJo);
                        }

                        walletDetailCache.put(walletDetailPoJo.getKey(), walletDetailPoJo);
                        redPackageCache.put(packagePoJo.getKey(), packagePoJo);
                        redPackageDetailCache.putAll(map);
                        walletCache.put(walletPoJo.getKey(), walletPoJo);
                        //提交事务
                        tx.commit();
                    }

                    //发送红包待领通知
                    sendRedPackageNotify(currentUserId, packagePoJo, request.getReceiverIds());

                    return ExecuteResult.success(redPackageId);

                } catch (Exception ex) {
                    return ExecuteResult.failure(ex.getMessage(), null);
                }
            } else {
                return ExecuteResult.failure("系统繁忙,请稍后再试", null);
            }
        }
    }

    private IExecuteResult<Boolean> validateReceiverIds(Long currentUserId, Long receiverId) {
        //检查是否是好友
        String sql = MessageFormat.format("select * from {0} where ownerId = ? and friendId = ?", IMFriendPoJo.CACHE_NAME);
        //检查红包创建人是否在接收人的黑名单中.
        String tmpSql = MessageFormat.format("select * from {0} where sessionId is null and userId = ? and creatorId = ?",
                BlackBillPoJo.CACHE_NAME);

        IMUserPoJo receiver = userCache.get(receiverId);
        IMFriendPoJo friendPoJo = IgniteQueryHelper.findOneBy(sql, new Object[]{receiverId, currentUserId},
                IMFriendPoJo.class, friendCache);
        if (friendPoJo == null) {
            return ExecuteResult.failure(MessageFormat.format("你不是{0}用户的好友,不能发送个人红包", receiver.getNickname()), false);
        }
        //读取可能存在的黑名单.
        BlackBillPoJo blackBillPoJo = IgniteQueryHelper.findOneBy(tmpSql, new Object[]{currentUserId, receiverId},
                BlackBillPoJo.class, blackBillCache);
        if (blackBillPoJo != null) {
            return ExecuteResult.failure(MessageFormat.format("你在{0}用户的黑名单中,无法发送个人红包",
                    receiver.getNickname()), false);
        }
        return ExecuteResult.success(true);
    }

    private IExecuteResult<Boolean> validateReceiverIds(Long groupId, List<Long> receiverIds) {
        Object[] params = new Object[1 + receiverIds.size()];
        params[0] = groupId;
        StringBuilder members = new StringBuilder();
        for (int i = 0; i < receiverIds.size(); i++) {
            members.append(" or memberId = ?");
            params[i + 1] = receiverIds.get(i);
        }
        String sql = MessageFormat.format("select * from {0} where checkStatus = 1 and sessionId = ? and ({1})",
                ChatSessionMemberPoJo.CACHE_NAME, members.substring(4));
        List<ChatSessionMemberPoJo> sessionMemberPoJos = IgniteQueryHelper.findAllBy(sql, params,
                ChatSessionMemberPoJo.class, sessionMemberCache);
        ChatSessionMemberPoJo sessionMemberPoJo = null;
        for (Long receiverId : receiverIds) {
            sessionMemberPoJo = sessionMemberPoJos.stream()
                    .filter(x -> Objects.equals(x.getMemberId(), receiverId))
                    .findFirst().orElse(null);
            if (sessionMemberPoJo == null) {
                IMUserPoJo userPoJo = userCache.get(receiverId);
                return ExecuteResult.failure(MessageFormat.format("用户【{0}】不是群成员，禁止领取群中的红包", userPoJo.getNickname()), null);
            }
            if (sessionMemberPoJo.isForbidRedPackage()) {
                return ExecuteResult.failure(MessageFormat.format("用户【{0}】已被禁止领取群中的红包", sessionMemberPoJo.getMemberNick()), null);
            }
        }
        return ExecuteResult.success(true);
    }

    private void sendRedPackageNotify(Long currentUserId, RedPackagePoJo packagePoJo, List<Long> receiverIds) {
        RedPackageType packageType = Arrays.stream(RedPackageType.values())
                .filter(x -> Objects.equals(packagePoJo.getType(), x.name()))
                .findFirst().orElse(null);
        assert packageType != null;

        int customType = 0;
        switch (packageType) {
            case PERSONAL -> customType = 22;
            case EXCLUSIVE -> customType = 21;
            default -> customType = 23;
        }

        IMUserPoJo creator = userCache.get(packagePoJo.getCreatorId());
        String avatar = "http://" + serviceProperties.getExternalClientService() + creator.getAvatar();

        String skinUrl = null;
        if (packagePoJo.getSkinId() != null) {
            RedPackageSkinPoJo skinPoJo = skinCache.get(packagePoJo.getSkinId());
            if (StringUtils.hasText(skinPoJo.getUrl())) {
                skinUrl = "http://" + serviceProperties.getExternalClientService() + skinPoJo.getUrl();
            }
        }

        List<String> adminIds = new ArrayList<>();
        if (packagePoJo.getSessionId() != null) {
            String sql = MessageFormat.format("select * from {0} where sessionId = ? and roleInSession <= 2 order by roleInSession",
                    ChatSessionMemberPoJo.CACHE_NAME);
            List<ChatSessionMemberPoJo> memberPoJos = IgniteQueryHelper.findAllBy(sql, new Object[]{packagePoJo.getSessionId()},
                    ChatSessionMemberPoJo.class, sessionMemberCache);
            for (ChatSessionMemberPoJo memberPoJo : memberPoJos) {
                adminIds.add("" + memberPoJo.getMemberId());
            }
        }

        JSONObject data = new JSONObject();
        JSONObject content = new JSONObject();
        data.put("type", customType);
        data.put("data", content);

        content.put("redPacketId", packagePoJo.getId());
        content.put("groupId", packagePoJo.getSessionId());
        content.put("customType", customType);
        content.put("title", packagePoJo.getTitle());
        content.put("amount", packagePoJo.getAmount());
        content.put("sendName", getNickname(creator.getId(), packagePoJo.getSessionId()));
        content.put("sendAvatar", avatar);
        content.put("sendLevel", creator.getGrade() != null ? creator.getGrade() : 0);
        content.put("received", packagePoJo.isReceivedAlready());
        content.put("claimed", false);
        content.put("skinUrl", skinUrl);
        content.put("fromUserId", creator.getId());
        content.put("createTime", packagePoJo.getCreateTime());
        content.put("adminIds", adminIds);

        if (StringUtils.hasText(packagePoJo.getReceiverIds())) {
            Long receiverId = Long.parseLong(packagePoJo.getReceiverIds().split(",")[0]);
            content.put("toUserId", receiverId);
            content.put("toUserName", getNickname(receiverId, packagePoJo.getSessionId()));
        } else {
            content.put("toUserId", null);
            content.put("toUserName", null);
        }

        StringBuilder error = new StringBuilder();
        boolean result = false;
        if (RedPackageType.PERSONAL.name().equals(packagePoJo.getType())) {
            result = iYunXinMsgSender.sendMsg(currentUserId, receiverIds.get(0), 100, data, error);
        } else {
            result = iYunXinMsgSender.sendMsgInSession(currentUserId, packagePoJo.getSessionId(), receiverIds,
                    100, data, error);
        }
        if (!result) {
            log.error("-------发送红包待领通知失败：" + error);
        }
    }

    private String getNickname(Long userId, Long sessionId) {
        if (sessionId == null) {
            IMUserPoJo userPoJo = userCache.get(userId);
            assert userPoJo != null;
            return userPoJo.getNickname();
        } else {
            String sql = MessageFormat.format("select * from {0} where sessionId = ? and memberId = ?", ChatSessionMemberPoJo.CACHE_NAME);
            ChatSessionMemberPoJo memberPoJo = IgniteQueryHelper.findOneBy(sql, new Object[]{sessionId, userId},
                    ChatSessionMemberPoJo.class, sessionMemberCache);
            assert memberPoJo != null;
            return memberPoJo.getMemberNick();
        }
    }

    private IExecuteResult<RedPackageDetailInfo> _receiveRedPackage(Long currentUserId, RedPackageType type, Long redPackageId) {
        try (IgniteSemaphore semaphore = ignite.semaphore("received_red_package:" + redPackageId, 1, true, true)) {
            if (semaphore.tryAcquire(3, TimeUnit.SECONDS)) {
                return __receiveRedPackage(currentUserId, redPackageId);
            } else {
                return ExecuteResult.failure("系统繁忙，请稍后再试", null);
            }
        } catch (Exception ex) {
            return ExecuteResult.failure("获取全局信号量异常：" + ex.getMessage(), null);
        }
    }

    private IExecuteResult<RedPackageDetailInfo> __receiveRedPackage(Long currentUserId, Long redPackageId) {
        try {
            String sql = MessageFormat.format("select * from {0} where redPackageId = ? and receivedAlready = ? and memberId = ?", RedPackageDetailPoJo.CACHE_NAME);
            RedPackageDetailPoJo detailPoJo1 = IgniteQueryHelper.findOneBy(sql, new Object[]{redPackageId, true, currentUserId},
                    RedPackageDetailPoJo.class, redPackageDetailCache);
            if (detailPoJo1 != null) {
                //已经领取过的人可以查询红包领取详情。
                return receiveRedPackageDetail(redPackageId);
            }

            Timestamp curTime = new Timestamp(System.currentTimeMillis());
            sql = MessageFormat.format("select * from {0} where redPackageId = ? and receivedAlready = ?" +
                    "and (memberId is null or memberId =?) and expireTime > ? order by mid limit 0, 1", RedPackageDetailPoJo.CACHE_NAME);
            RedPackageDetailPoJo detailPoJo = IgniteQueryHelper.findOneBy(sql, new Object[]{redPackageId, false, currentUserId, curTime},
                    RedPackageDetailPoJo.class, redPackageDetailCache);
            if (detailPoJo == null) {
                //领取不到则返回红包领取详情。
                return receiveRedPackageDetail(redPackageId);
            }
            detailPoJo.setMemberId(currentUserId);
            detailPoJo.setReceivedTime(curTime);
            detailPoJo.setReceivedAlready(true);

            RedPackageType redPackageType = Arrays.stream(RedPackageType.values())
                    .filter(x -> x.name().equals(detailPoJo.getRedPackageType()))
                    .findFirst().orElse(null);
            assert redPackageType != null;

            WalletEventType eventType = null;
            switch (redPackageType) {
                case PERSONAL -> eventType = WalletEventType.RECEIVE_PERSONAL_RED_PACKET;
                case EXCLUSIVE -> eventType = WalletEventType.RECEIVE_EXCLUSIVE_RED_PACKET;
                default -> eventType = WalletEventType.RECEIVE_LUCKY_RED_PACKET;
            }

            IMUserPoJo userPoJo = userCache.get(currentUserId);
            try (IgniteSemaphore walletSemaphore = ignite.semaphore("wallet:" + userPoJo.getWalletId(), 1, true, true)) {
                if (walletSemaphore.tryAcquire(3, TimeUnit.SECONDS)) {
                    try {
                        //创建乐观串行事务。
                        try (Transaction tx = ignite.transactions().txStart(TransactionConcurrency.OPTIMISTIC,
                                TransactionIsolation.SERIALIZABLE)) {

                            //修改钱包的余额。
                            WalletPoJo walletPoJo = walletCache.get(userPoJo.getWalletId());
                            walletPoJo.setInsert(false);
                            Long balance = walletPoJo.getBalance() + detailPoJo.getAmount();
                            walletPoJo.setBalance(balance);
                            //创建钱包余额变更记录。
                            WalletChangeDetailPoJo changeDetailPoJo = WalletChangeDetailPoJo.builder()
                                    .id(IdSnowFlake.getId())
                                    .eventType(eventType.name())
                                    .eventId(detailPoJo.getId()) //事件ID填充的是红包详情的ID
                                    .amount(detailPoJo.getAmount())
                                    .walletId(walletPoJo.getId())
                                    .balance(balance)
                                    .createTime(new Timestamp(System.currentTimeMillis()))
                                    .creatorId(currentUserId)
                                    .version(0L)
                                    .insert(true)
                                    .build();

                            sql = MessageFormat.format("select count(*) from {0} where receivedAlready = false and mid != ? and redPackageId = ?", RedPackageDetailPoJo.CACHE_NAME);
                            long count = IgniteQueryHelper.getCountBy(sql, new Object[]{detailPoJo.getId(), redPackageId}, redPackageDetailCache);
                            if (count == 0) {
                                RedPackagePoJo redPackagePoJo = redPackageCache.get(redPackageId);
                                redPackagePoJo.setReceivedAlready(true);
                                redPackagePoJo.setInsert(false);
                                redPackageCache.put(redPackagePoJo.getKey(), redPackagePoJo);
                            }

                            redPackageDetailCache.put(detailPoJo.getKey(), detailPoJo);
                            walletCache.put(walletPoJo.getKey(), walletPoJo);
                            walletDetailCache.put(changeDetailPoJo.getKey(), changeDetailPoJo);
                            tx.commit();
                        }
                    } catch (Exception ex) {
                        return ExecuteResult.failure("领取红包异常：" + ex.getMessage(), null);
                    }
                }else{
                    return ExecuteResult.failure("系统繁忙,请稍后再试", null);
                }
            }

            IExecuteResult<RedPackageDetailInfo> data = receiveRedPackageDetail(redPackageId);
            data.getData().setActionType(4);
            return data;

        } catch (Exception ex) {
            return ExecuteResult.failure("领取红包异常：" + ex.getMessage(), null);
        }
    }

    private List<ReceivedRedPackageItem> convertToReceivedRedPackageItemList(List<RedPackageDetail> details) {
        List<ReceivedRedPackageItem> list = new ArrayList<>();
        for (RedPackageDetail detail : details) {
            //获取红包对象
            assert detail.getRedPackage() != null;
            RedPackage redPackage = redPackageRepo.findById(detail.getRedPackage().getId()).orElse(null);
            assert redPackage != null;
            IMUser creator = imUserRepo.findById(redPackage.getCreator().getId()).orElse(null);
            assert creator != null;

            String nickName = creator.getNickname();
            if (redPackage.getSession() != null) {
                //如果群组对象不为空，则昵称要取用户的群昵称。
                ChatSessionMember sessionMember = sessionMemberRepo.findOneByMemberIdAndSessionId(detail.getMemberId(),
                        redPackage.getSession().getId());
                if (sessionMember != null) {
                    nickName = sessionMember.getMemberNick();
                }
            }

            String avatar = "http://" + serviceProperties.getExternalClientService() + creator.getAvatar();

            ReceivedRedPackageItem item = ReceivedRedPackageItem.builder()
                    .redPackageId(redPackage.getId())
                    .type(redPackage.getType())
                    .typeName(redPackage.getType().getName())
                    .senderId(redPackage.getCreator().getId())
                    .senderNick(nickName)
                    .senderAvatar(avatar)
                    .amount(detail.getAmount())
                    .createTime(detail.getCreateTime().getTime())
                    .receivedTime(detail.getReceivedTime() != null ? detail.getReceivedTime().getTime() : null)
                    .build();
            list.add(item);
        }
        return list;
    }

    public List<SendRedPackageItem> convertToSendRedPackageItemList(List<RedPackage> redPackages) {
        List<SendRedPackageItem> result = new ArrayList<>();
        Map<Long, String> skinUrlMap = new LinkedHashMap<>();
        long receiverCount = 0;
        Long receivedAmount;
        Long refundedAmount;
        for (RedPackage redPackage : redPackages) {
            if (!skinUrlMap.containsKey(redPackage.getId()) && redPackage.getSkinId() != null) {
                //查找红包使用的皮肤的URL
                redPackageSkinRepo.findById(redPackage.getSkinId()).ifPresent(skin -> skinUrlMap.put(skin.getId(), skin.getUrl()));
            }
            //获取红包领取详情记录列表。
            List<RedPackageDetail> packageDetails = redPackageDetailRepo.findAllByRedPackageId(redPackage.getId());
            if (!CollectionUtils.isEmpty(packageDetails)) {
                receiverCount = packageDetails.stream().filter(RedPackageDetail::isReceivedAlready).count();
                receivedAmount = packageDetails.stream().filter(RedPackageDetail::isReceivedAlready)
                        .map(RedPackageDetail::getAmount).reduce(Long::sum).orElse(0L);
                refundedAmount = packageDetails.stream().filter(RedPackageDetail::isRefunded)
                        .map(RedPackageDetail::getAmount).reduce(Long::sum).orElse(0L);

                String url = skinUrlMap.get(redPackage.getSkinId());
                SendRedPackageItem item = SendRedPackageItem.builder()
                        .id(redPackage.getId())
                        .skinId(redPackage.getSkinId())
                        .skinUrl(url)
                        .title(redPackage.getTitle())
                        .splitCount(redPackage.getSplitCount())
                        .amount(redPackage.getAmount())
                        .receiverCount(receiverCount)
                        .receivedAmount(receivedAmount)
                        .refundedAmount(refundedAmount)
                        .receivedAlready(redPackage.isReceivedAlready())
                        .expireTime(packageDetails.get(0).getExpireTime().getTime())
                        .createTime(redPackage.getCreateTime().getTime())
                        .build();
                result.add(item);
            }
        }
        return result;
    }
}

